Content
Let’s talk about…
- User-interface / Layout
- Reactivity / Logic
- Awesome visualizations
A hands-on workshop
2024-03-14
Shiny is an R package that makes it easy to build interactive web apps in R
Apps can be
Some examples:
You need to install these software packages in order to follow along with the examples of today:
And a couple of R packages:
Please find the slides and code snippets here:
Learn about the structure of a shiny application.
Learn how to create shiny apps from a template.
Learn how to think in terms of inputs and outputs.
Write your own apps (using simulated data, real data or your data)
Let’s talk about…
We first load the shiny package and define a shinyApp, which really is only a function call with two arguments.
The ui specifies the visible user interface
The server is invisible and is responsible for all computations
server monitors inputsInputs have unique ids that correspond to server-side variables, a label, a starting value and extra options (e.g., range restrictions, etc.)
textInput(inputId="familyname", label="Family name:", value="Steve Miller" )
or
numericInput(inputId="age", label="Age (in years):", value=1, min=0, max=150 )
On the server, we will be able to access variables input$familyname and input$age
Many more, e.g. Tabsets - see tabsetPanel()
Example output elements (placeholders for dynamic content):
textOutput() or htmlOutput()plotOutput()tableOutput()You can use
to find other output functions in shiny.
Each *Output() function has a corresponding render*() server-side function. For example:
textOutput() \(\rightarrow\) renderText()plotOutput() \(\rightarrow\) renderPlot()tableOutput() \(\rightarrow\) renderTable()Inputs are accessed in the server function via the input argument.
Inputs are reactive, meaning that changes trigger updates to outputs.
Demo: We write a simple calculator that adds +1 to a number we enter.
The simplest structure of a reactive program involves just a source and an endpoint:
flowchart LR subgraph outputs re([result]) end subgraph inputs n1([number]) end n1 --> re
R/demo1.R
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Calculator"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("number",
"Number", value=0)
),
# Show a plot of the generated distribution
mainPanel(
h3("Result"),
textOutput("result")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$result <- renderText({
return(input$number + 1)
})
}
# Run the application
shinyApp(ui = ui, server = server)Large language models are great companions for programming
Here is a ChatGPT link (requires Microsoft or Google account) to answer your questions (but please ask us as well any time)
Copy the code from the previous slide (or open R/demo1.R) and run it in R
Check that you are able successfully run the shiny app and are able to interact with it.
The reactive diagram of this solution shows two inputs and one output:
flowchart LR subgraph outputs re([result]) end subgraph inputs n1([number1]) n2([number2]) end n1 --> re n2 --> re
R/solution1_1.R
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Calculator"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("n1",
"Number", value=0),
numericInput("n2",
"Number", value=0)
),
# Show a plot of the generated distribution
mainPanel(
textOutput("result")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$result <- renderText({
return(input$n1+input$n2)
})
}
# Run the application
shinyApp(ui = ui, server = server)Continue with your code (or from R/solution1_1.R) and add a menu to choose different operators (e.g., plus, minus, …)
For example, add a selectInput(inputId, label, choices)
Add server-side logic to implement the different operators
flowchart LR subgraph outputs re([result]) end subgraph inputs n1([number1]) n2([number2]) op([operator]) end n1 --> re n2 --> re op --> re
R/solution1_2.R
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Calculator"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("n1",
"Number", value=0),
numericInput("n2",
"Number", value=0),
selectInput("operator","Operator",c("+","-","/","*"))
),
# Show a plot of the generated distribution
mainPanel(
textOutput("result")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$result <- renderText({
result <- switch (input$operator,
"+" = input$n1+input$n2,
"-" = input$n1-input$n2,
"/" = input$n1/input$n2,
"*" = input$n1*input$n2
)
return(result)
})
}
# Run the application
shinyApp(ui = ui, server = server)We can use HTML elements to style text. E.g.,
<b>Bold</b> or <i>Italics</i>,h1>First-level heading</h> <h2>Second-level heading</h2>, ...
In UI as static or dynamic elements:
On the server:
R/solution1_3.R
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Calculator"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("n1",
"Number", value=0),
selectInput("operator","Operator",c("+","-","/","*")),
numericInput("n2",
"Number", value=0)
),
# Show a plot of the generated distribution
mainPanel(
shiny::h2("Result:"),
htmlOutput("result")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$result <- renderText({
result <- switch (input$operator,
"+" = input$n1+input$n2,
"-" = input$n1-input$n2,
"/" = input$n1/input$n2,
"*" = input$n1*input$n2
)
result <- paste0("<h2>",result,"</h2>")
return(result)
})
}
# Run the application
shinyApp(ui = ui, server = server)We are going to use the penguins dataset from palmerpenguins
| species | island | bill_length_mm | bill_depth_mm | flipper_length_mm | body_mass_g | sex | year |
|---|---|---|---|---|---|---|---|
| Adelie | Torgersen | 39.1 | 18.7 | 181 | 3750 | male | 2007 |
| Adelie | Torgersen | 39.5 | 17.4 | 186 | 3800 | female | 2007 |
| Adelie | Torgersen | 40.3 | 18.0 | 195 | 3250 | female | 2007 |
| Adelie | Torgersen | NA | NA | NA | NA | NA | 2007 |
| Adelie | Torgersen | 36.7 | 19.3 | 193 | 3450 | female | 2007 |
| Adelie | Torgersen | 39.3 | 20.6 | 190 | 3650 | male | 2007 |
R/challenge2.R
library(shiny)
library(tidyverse)
library(palmerpenguins)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Penguins"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
# <------- here go input elements
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("plot1"),
plotOutput("plot2"),
textOutput("text1")
# <------- add more outputs here if needed
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
output$plot1 <- renderPlot({
penguins %>% ggplot(aes(x=body_mass_g,y=bill_length_mm))+
geom_point()+
geom_smooth(method = "lm")
})
output$plot2 <- renderPlot({
# <------ generate plot here (ggplot, or base R)
})
output$text1 <- renderText({
# <------- generate some text here
})
}
# Run the application
shinyApp(ui = ui, server = server)Copy the code from the previous slide (or open R/challenge2.R) and run it in R
Add logic to create a second plot as output plot2 on the server
Add extra inputs (e.g., add a selectInput for subgroup selection of penguin species) or add a rangeInput to display only certain ranges of years, or make point size adjustable by a given variable (selectInput or a checkboxInput).
Assume a range input (sliderInput(value=c(0,10))) that filters data
Filter logic should be executed only once for every relevant output
Never copy&paste server logic, instead use a reactive element
flowchart LR subgraph outputs pl1([plot1]) pl2([plot2]) tx1([text1]) end compute([compute]) subgraph inputs slider1([slider1]) n1([number1]) rn1([range1]) ck1([ck1]) end compute --> pl1 compute --> pl2 rn1 --> compute n1 --> pl1
Their primary use is similar to a function in an R script, they help to
avoid repeating yourself
decompose complex computations into smaller / more modular steps
can improve computational efficiency by breaking up / simplifying reactive dependencies
R/demo3.R
library(shiny)
library(tidyverse)
library(palmerpenguins)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Penguins"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("rng", "Range ",value=c(3000,5000),min=2700, max=6300),
selectInput("size", label="Size", choices=c("flipper_length_mm","bill_length_mm")),
checkboxInput("grp",label="Subgroups", value=TRUE)
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("plot1"),
plotOutput("plot2"),
textOutput("text1")
)
)
)
# Define server logic required to draw a histogram
server <- function(input, output) {
penguins_filtered <- reactive({
penguins %>% filter(body_mass_g >= input$rng[1] & body_mass_g <= input$rng[2])
})
output$plot1 <- renderPlot({
wf <- NULL
if (input$grp) {
wf <- facet_wrap(~species)
}
penguins_filtered() %>% ggplot(aes(x=body_mass_g,y=bill_length_mm))+
geom_point(aes_string(size=input$size))+
geom_smooth(method = "lm")+
wf
})
output$plot2 <- renderPlot({
if (input$grp) {
penguins_filtered() %>% ggplot(aes(x=flipper_length_mm,fill=species))+geom_histogram()
} else {
penguins_filtered() %>% ggplot(aes(x=flipper_length_mm))+geom_histogram()
}
})
output$text1 <- renderText({
paste0("<b>There</b> are ",nrow(penguins_filtered()), " penguins in the data set")
})
}
# Run the application
shinyApp(ui = ui, server = server)here package) and that all dependencies are loadedPackage shinydashboard has some nice GUI elements for dashboards:
R/demo7.R
library(shinydashboard)
ui <- dashboardPage(
dashboardHeader(title = "Value boxes"),
dashboardSidebar(),
dashboardBody(
fluidRow(
# A static valueBox
valueBox(20, "New Orders", icon = icon("credit-card")),
# Dynamic valueBox
valueBoxOutput("progressBox"),
),
fluidRow(
# Clicking this will increment the progress amount
box(width = 4, actionButton("count", "Do some work"))
)
)
)
server <- function(input, output) {
output$progressBox <- renderValueBox({
if (input$count < 10) {
ic <- icon("thumbs-down")
col <- "red"
} else {
ic <- icon("thumbs-up")
col <- "green"
}
valueBox(
paste0(input$count, "%"), "Progress", icon = ic,
color = col
)
})
}
shinyApp(ui, server)Shiny is useful for simulating data (multivariate distributions, network graphs, agents, …)
reactive() to generate our dataset, so that it can be reused in different placesdownloadButton and downloadHandler allow us to download the simulated data files for later analyses R/demo6.R
library(shiny)
# Define UI for application that draws a histogram
ui <- fluidPage(
# Application title
titlePanel("Simulation"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("N",
"Sample Size", value=100),
downloadButton("download")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("graph")
)
)
)
# Define server logic
server <- function(input, output) {
sim <- reactive({
# <----- create a simulated dataset here
})
# return the dataset as file
output$download = downloadHandler(
filename = function() {
"simulation.csv"
},
content = function(file) {
readr::write_csv(sim(), file)
}
)
output$graph <- renderPlot({
# <------ do some plotting here
})
}
# Run the application
shinyApp(ui = ui, server = server)Copy the code from the previous slide (or open R/demo6.R) and run it in R
rnorm or MASS::mvrnorm) R/solution6.R
library(shiny)
# Define UI for application
ui <- fluidPage(
# Application title
titlePanel("Simulation"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
numericInput("N",
"Sample Size", value=100),
numericInput("r",
"Correlation", value=0),
downloadButton("download")
),
# Show a plot of the generated distribution
mainPanel(
plotOutput("graph")
)
)
)
# Define server logic
server <- function(input, output) {
sim <- reactive({
r = input$r
N = input$N
df <- MASS::mvrnorm(n=N, mu=c(0,0),
Sigma=matrix(c(1,r,
r,1),
nrow=2))
df <- data.frame(df)
names(df) <- c("x","y")
return(df)
})
output$download = downloadHandler(
filename = function() {
"simulation.csv"
},
content = function(file) {
readr::write_csv(sim(), file)
}
)
output$graph <- renderPlot({
sim() %>% ggplot(aes(x=x,y=y))+ geom_point()+geom_smooth(method = "lm")
})
}
# Run the application
shinyApp(ui = ui, server = server)The Shiny User Showcase is comprised of contributions from the Shiny app developer community.
To the extent possible under law and unless otherwise noted, Andreas and Leonie have waived all copyright and related or neighboring rights to this workshop document and the accompanying R source codes. This work is published from: Deutschland/Germany.
Some parts of this workshop are inspired by work by Colin Rundel (https://github.com/rstudio-conf-2022/get-started-shiny/), which is provided under https://creativecommons.org/licenses/by/4.0/.
Illustrations by undraw https://undraw.co (see their license https://undraw.co/license)
Thank you for being on this journey with us!
Andreas (https://www.brandmaier.de; also find me on Twitter), Bluesky, Linkedin)
MSB Medical School Berlin